Lambda+Glue+Step Functionsの構成をServerless FrameworkとAWS SAMのそれぞれでデプロイしてみた
データアナリティクス事業本部のueharaです。
今回は、Lambda+Glue+Step Functionsの構成をServerless FrameworkとAWS SAMのそれぞれでデプロイしてみたいと思います。
はじめに
2023年の10月に、Serverless FrameworkがV.4から有料化されることが発表されました。
これまでAWS上でETL処理を行うリソースのデプロイにServerless Frameworkを多用していたのですが、有料化に伴い別の手段も検討したく、本記事ではAWS Serverless Application Model (AWS SAM)を利用したいと思います。
今回デプロイを検証するのは、比較的軽量なETLでありがちな以下の構成になります。
S3の特定のパスにファイルがPutされたことをEventBridgeで検知し、Step Functionsを起動します。
Step FunctionsのワークフローとしてはLambda→Glueと処理が進むような流れを考えます。
※S3は既に構築済みのものとし、Serverless Framework及びSAMでのデプロイは今回は行いません。
Serverless Frameworkでのデプロイ
まずはServerless Frameworkによるデプロイを行います。
フォルダは構成は以下の通りです。
. ├── glue_scripts │ └── test_glue.py ├── handler │ └── test_func.py ├── package.json └── serverless.yml
ファイルの用意
test_glue.py
test_glue.py
にはGlueで実行するスクリプトを記載します。
今回は検証なので一定時間Sleepするだけの処理を記載することにします。
import sys import time def main(argv): print("start") # sleep 5 minutes time.sleep(300) print("end") main(sys.argv)
こちらのファイルをGlueから参照できるようにするため任意のS3バケットにアップロードしておきます。
私は s3://cm-da-uehara/glue-scripts/test_glue.py
としてアップロードしました。
test_func.py
test_func.py
にはLambdaで実行するスクリプトを記載します。
Glueのスクリプトと同様に、単純に一定時間Sleepするだけの処理を記載することにします。
import time def lambda_handler(event, context): print("start") # sleep 1 minute time.sleep(60) print("end") return {"message": "success"}
package.json
package.json
に今回のデプロイに関連するリソースの依存関係を記載しておきます。
{ "dependencies": { "serverless": "^3.19.0" }, "devDependencies": { "serverless-python-requirements": "^5.4.0", "serverless-step-functions": "^3.20.1" }, "name": "uehara-sls-test" }
Serverless Frameworkを用いたStep Functionsのデプロイにはserverless-step-functionsプラグインが必要になるため、そちらも記載をしています。
serverless.yml
Serverless Frameworkでのデプロイにおいて一番の肝になる serverless.yml
は以下の通りに記載します。
service: uehara-test-app-sls frameworkVersion: '3' configValidationMode: error plugins: - serverless-step-functions provider: name: aws runtime: python3.9 stackName: ${self:service} stage: ${env:ENV, 'dev'} region: ap-northeast-1 deploymentBucket: name: cm-da-uehara timeout: 180 # 180 seconds memorySize: 128 package: individually: true patterns: - '!handler/**' - '!.git/**' - '!.gitignore' - '!.serverless' - '!.serverless/**' - '!package.json' - '!package-lock.json' - '!serverless.yml' - '!yarn.lock' - '!node_modules' - '!node_modules/**' - '!__pycache__' functions: # Lambda MyLambdaFunction: handler: handler/test_func.lambda_handler name: "uehara-sls-test-lambda" role: MyLambdaFunctionRole package: patterns: - 'handler/test_func.py' resources: Resources: # Lambda Role MyLambdaFunctionRole: Type: AWS::IAM::Role Properties: RoleName: "uehara-sls-test-lambda-role" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: - lambda.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole # Glue Job MyGlueJob: Type: AWS::Glue::Job Properties: Role: !GetAtt GlueJobRole.Arn GlueVersion: '3.0' Name: uehara-sls-test-glueJob DefaultArguments: "library-set": "analytics" Command: Name: pythonshell ScriptLocation: "s3://cm-da-uehara/glue-scripts/test_glue.py" PythonVersion: "3.9" ExecutionProperty: MaxConcurrentRuns: 3 MaxCapacity: 0.0625 MaxRetries: 0 # Glue Job Role GlueJobRole: Type: AWS::IAM::Role Properties: RoleName: "uehara-sls-test-glueJob-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: glue.amazonaws.com Action: "sts:AssumeRole" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole - arn:aws:iam::aws:policy/AmazonS3FullAccess # Step Functions Role MyStepFunctionsRole: Type: AWS::IAM::Role Properties: RoleName: "uehara-sls-test-sf-role" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - states.ap-northeast-1.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaRole - arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole # EventBridge Role MyEventBridgeRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - "states:StartExecution" Resource: - "arn:aws:states:*:*:stateMachine:$uehara-sls-test-sf" RoleName: uehara-sls-test-eventbridge-role stepFunctions: # Step Functions stateMachines: MyStepFunctions: name: "uehara-sls-test-sf" role: !GetAtt MyStepFunctionsRole.Arn definition: Comment: "Test Step Functions" StartAt: InvokeLambda States: InvokeLambda: Type: Task Resource: !GetAtt MyLambdaFunction.Arn Next: InvokeGlueJob InvokeGlueJob: Type: Task Resource: "arn:aws:states:::glue:startJobRun.sync" Parameters: JobName: !Ref MyGlueJob End: true events: - cloudwatchEvent: name: "uehara-sls-test-sf-event" iamRole: !GetAtt MyEventBridgeRole.Arn event: source: - "aws.s3" detail-type: - "Object Created" detail: bucket: name: - "cm-da-uehara" object: key: - prefix: 'tmp/'
特徴として、Lambda関数についてはfunctions
セクションに、Step FunctionsについてはstepFunctions
セクションに記載を行います。
GlueのScriptLocation
については、ご自身でアップロードしたGlueのスクリプトを保管しているS3のURIを指定して下さい。
Step Functionsの起動トリガーについて、cm-da-uehara
というS3バケットにオブジェクトキーのPrefixにtmp/
がついたものがPutされたら起動するというものになっています。
デプロイ
以下コマンドでデプロイを行います。
$ sls deploy --verbose
デプロイが完了すると、以下のようにStep Functionsが実行できるようになっているかと思います。
実行
指定したS3バケットのtmp/
に適当なデータをPutしてStep Functionsを起動すると、Lambda→Glueの順に処理が実施されます。
それぞれの処理はただ単にSleepをしているだけなので、問題なく終了するかと思います。
AWS SAMでのデプロイ
次に、SAMで同じことをしてみます。
フォルダは構成は以下の通りです。
. ├── glue_scripts │ └── test_glue.py ├── handler │ └── test_func.py ├── samconfig.toml └── template.yaml
ファイルの用意
【注】 test_glue.py
とtest_func.py
はServerless Frameworkで記載した内容と同じのため、ここでは割愛します。
samconfig.toml
samconfig.toml
はAWS SAM CLIの設定ファイルになります。
記載方法についてはこちらのドキュメントに記載がありますので、そちらを参考頂ければと思います。
今回は以下のように設定してみました。
version = 0.1 [default] region = "ap-northeast-1" [default.build.parameters] debug = true [default.deploy.parameters] stack_name = "uehara-test-app" s3_bucket = "cm-da-uehara" s3_prefix = "sam-deploy" capabilities = "CAPABILITY_NAMED_IAM" confirm_changeset = true
1点特筆しておくと、カスタム名を持つIAMリソースを作成する場合は、CAPABILITY_NAMED_IAM
の指定が必要になりますので、今回そちらを設定しています。
template.yaml
template.yaml
がSAMのテンプレートファイルになります。
※Serverless Frameworkでいうところのserverless.yml
に相当。
記載内容は以下の通りです。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: "Test SAM Application" Globals: Function: Timeout: 180 # 180 seconds MemorySize: 128 Resources: # Lambda MyLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: "uehara-sam-test-lambda" Role: !GetAtt MyLambdaFunctionRole.Arn CodeUri: handler/ Handler: test_func.lambda_handler Runtime: python3.9 Architectures: - x86_64 # Lambda Role MyLambdaFunctionRole: Type: AWS::IAM::Role Properties: RoleName: "uehara-sam-test-lambda-role" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: - lambda.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole # Glue Job MyGlueJob: Type: AWS::Glue::Job Properties: Role: !GetAtt MyGlueJobRole.Arn GlueVersion: '3.0' Name: "uehara-sam-test-glueJob" DefaultArguments: "library-set": "analytics" Command: Name: pythonshell ScriptLocation: "s3://cm-da-uehara/glue-scripts/test_glue.py" PythonVersion: "3.9" ExecutionProperty: MaxConcurrentRuns: 3 MaxCapacity: 0.0625 MaxRetries: 0 # Glue Job Role MyGlueJobRole: Type: AWS::IAM::Role Properties: RoleName: "uehara-sam-test-glueJob-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: glue.amazonaws.com Action: "sts:AssumeRole" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole - arn:aws:iam::aws:policy/AmazonS3FullAccess # Step Functions MyStepFunctions: Type: AWS::Serverless::StateMachine Properties: Name: "uehara-sam-test-sf" Definition: Comment: "Test Step Functions" StartAt: InvokeLambda States: InvokeLambda: Type: Task Resource: !GetAtt MyLambdaFunction.Arn Next: InvokeGlueJob InvokeGlueJob: Type: Task Resource: "arn:aws:states:::glue:startJobRun.sync" Parameters: JobName: !Ref MyGlueJob End: true Role: !GetAtt MyStepFunctionsRole.Arn Events: S3Event: Type: EventBridgeRule Properties: RuleName: "uehara-sam-test-sf-event" Pattern: source: - aws.s3 detail-type: - "Object Created" detail: bucket: name: - "cm-da-uehara" object: key: - prefix: "tmp/" # Step Functions Role MyStepFunctionsRole: Type: AWS::IAM::Role Properties: RoleName: "uehara-sam-test-sf-role" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - states.ap-northeast-1.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaRole - arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
Lambdaのメモリサイズやタイムアウト等の設定値については、先にServerless Frameworkでデプロイしたものと同じ値にしています。
Serverless Frameworkと違い、LambdaやStep Functionsについても全てResources
セクションに記載をします。
Lambdaについて、Serverless Frameworkではserverless.yml
のトップのpackage
→patterns
で明示的に除外設定を行わなければトップ配下の全てのリソースが各関数にアップロードされる仕組みなのですが、SAMではCodeUri
にてLambdaにアップロードするディレクトリを明示的に指定します。
Step Functionsについて、起動のためのEventBridgeのルールはEvents
プロパティに記載することができます。
Serverless Frameworkの時と違いEventBridge用のIAMロールを明示的に用意しておりませんが、上記の記載で指定のステートマシンのみを起動することが許可されたポリシーを持つIAMロールが自動で作成されアタッチされます。
また、Step Functionsの定義についてはDefinition
プロパティにてyaml形式で記載することができますが、以下のように別ファイルに外出しして記載することもできます。
MyStepFunctions: Type: AWS::Serverless::StateMachine Properties: Name: "uehara-sam-test-sf" DefinitionUri: sfs/xxx.json Role: !GetAtt MyStepFunctionsRole.Arn ...
デプロイ
以下コマンドでデプロイを行います。
$ sam deploy
デプロイが完了すると、以下のようにStep Functionsが実行できるようになっているかと思います。
明示的に作成していなかったEventBridgeのIAMロールついても念のため確認すると、以下の通り指定のStep Functionsを起動できるのポリシーのみを持つものになっていました。
実行
Serverless Frameworkでのデプロイと同じ内容のものをデプロイしているので、挙動に違いはありません。
Step Functionsが起動されると、Lambda→Glueの順に処理が実施されます。
SAMを利用して気になった点
基本的にはCloudFormationベースでの記載となるため、Serverless FrameworkとSAMで記述方法が似ているところも多いですが、個人的にSAMについて以下の点が気になりました。
yamlファイルが分割できない
Serverless Frameworkではyamlファイルの記述を分割し、ファイルを分けることができます。
例:
functions: # Lambda関数を3つ用意 - ${file(lambda/lambda_a.yml):functions} - ${file(lambda/lambda_b.yml):functions} - ${file(config/lambda_c.yml):functions}
これにより、複数人での共同開発においてserverless.yml
の競合をなるべく抑えることができ、またserverless.yml
がダラダラと長くならないため可読性も向上します。
SAMでもファイル分割自体をすることができますが、それは「スタックも別に作成する」ということになり、スタックを別々に作成しネストする他ありません。
SAMで1つのスタックにまとめたいとなるとtemplate.yaml
が肥大化することになるので、その点1つのスタックを複数ファイルに分割して記載できるServerless Frameworkは便利だと感じました。
変数が使えない
Serverless Frameworkにはcustom
というセクションがあり、自由に変数を定義できます。
SAMでもMappings
セクションにパラメータ値をゴリゴリ書くこともできますが、Serverless Frameworkの変数ほどの汎用性はないためその点についても惜しいポイントだと感じます。
最後に
今回は、Lambda+Glue+Step Functionsの構成をServerless FrameworkとAWS SAMのそれぞれでデプロイしてみました。
参考になりましたら幸いです。